<?php

namespace app\api\service;

use app\api\model\OrderProduct;
use app\api\model\Product;
use app\api\model\Order as OrderModel;
use app\api\model\UserAddress;
use app\api\lib\enum\OrderStatusEnum;
use app\api\exception\OrderException;
use app\api\exception\UserException;
use think\Db;
use think\Exception;

/**
 * 订单类
 * 订单做了以下简化：
 * 创建订单时会检测库存量，但并不会预扣除库存量，因为这需要队列支持
 * 未支付的订单再次支付时可能会出现库存不足的情况
 * 所以，项目采用3次检测
 * 1. 创建订单时检测库存
 * 2. 支付前检测库存
 * 3. 支付成功后检测库存
 */
class Order
{

    /**
     * 订单中的商品数组
     *  product_id=>count
     * @var Array 
     */
    protected $order_products;

    /**
     * Product中的商品数据
     * @var Array 
     */
    protected $db_products;

    /**
     * 订单的所有者id
     * @var type 
     */
    protected $uid;

    /**
     * 商品状态
     * @var Array 
     */
    protected $product_status = [];

    /**
     * 订单状态
     * @var Array 
     */
    protected $order_status = [
        'valid' => true,
        'order_price' => 0,
        'product_status_arr' => []
    ];

    function __construct($uid = '', $order_products = [])
    {
        $this->uid = $uid;
        $this->order_products = array_column($order_products, 'count', 'product_id');
    }

    /**
     * 下单,返回状态
     * 开启事务,获取商品信息,判断库存(锁),创建订单快照,保存数据(order_produ,product,order)
     * @param int $uid 用户id
     * @param array $order_products 订单商品列表
     * @return array 订单商品状态
     * @throws Exception
     */
    public function place()
    {
        Db::startTrans();
        try
        {
//         判断库存(锁)
            $product_status = $this->getProductStatus();
//         创建订单快照
            $snap = $this->snapOrder();
//         保存数据
            $order_no = $this->makeOrderNo();
            $order = new OrderModel();
            $snap['user_id'] = $this->uid;
            $snap['order_no'] = $order_no;
            $order->save($snap);

            $order_id = $order->id;
            $create_time = $order->create_time;

            $order_product_data = [];
            $product_data = [];
            foreach ($this->product_status as $key => $product_detail)
            {
                $order_product_data[$key] = [
                    'order_id' => $order_id,
                    'product_id' => $key,
                    'count' => $product_detail['count'],
                ];
                $product_data[$key] = [
                    'id' => $key,
                    'stock' => $product_detail['stock'] - $product_detail['count'],
                ];
            }
//            库存应该是在支付成功之后才减少,这样可以防止恶意占用库存;
//            $product_model = new Product();
//            $product_model->isUpdate()->saveAll($product_data);
            $orderProduct = new OrderProduct();
            $orderProduct->isUpdate(FALSE)->saveAll($order_product_data);
            Db::commit();
            return [
                'order_no' => $order_no,
                'order_id' => $order_id,
                'create_time' => $create_time
            ];
        }
        catch (Exception $ex)
        {
            Db::rollback();
            throw $ex;
        }
    }

    /**
     * 检测库存
     * @param string $order_no 订单号
     * @return array 订单商品状态
     * @throws Exception
     */
    public function checkOrderStock($order_id)
    {
        $order_product_arr = OrderProduct::getInfoByOrderId($order_id)->toArray();
        $this->order_products = array_column($order_product_arr, 'count', 'product_id');
        $this->getProductStatus();
    }

    /**
     * 查找订单,改变订单状态,推送消息
     * @param type $order_id
     * @param type $jumpPage
     */
    public function delivery($order_id, $jumpPage = '')
    {
        $order_model = OrderModel::get($order_id);
        if (empty($order_model))
        {
            throw new OrderException();
        }
        if ($order_model->status != OrderStatusEnum::PAID)
        {
            $option = [
                'msg' => '没付款或者你已经更新过订单了，不要再刷了',
                'errorCode' => 80002,
                'code' => 403
            ];
            throw new OrderException($option);
        }
        $order_model->status = OrderStatusEnum::DELIVERED;
        $res = $order_model->isUpdate()->save();
        $message = new DeliveryMessage();
        return $message->sendDeliveryMessage($order, $jumpPage) && $res;
    }

    /**
     * 先设置好$this->order_products
     * 校对订单产品在product表中是否存在,库存是否充足
     * @return boolean
     * @throws OrderException
     */
    private function getProductStatus()
    {
        $this->db_products = $this->getProductsByOrder($this->order_products);
        $ret = [];
        $status_template = [
            'id' => null,
            'count' => 0,
            'name' => '',
            'total_price' => 0,
            'price' => 0,
            'stock' => 0,
            'main_img_url' => '',
        ];
        $product_status = [];
        foreach ($this->order_products as $product_id => $count)
        {
            if (!empty($this->db_products[$product_id]))
            {
                $product_detail = $this->db_products[$product_id];
                $have_stock = $product_detail['stock'] - $count >= 0 ? true : false;
                if ($have_stock == FALSE)
                {
                    $option = [
                        'msg' => $product_detail['name'] . ' 库存不足，操作失败',
                    ];
                    throw new OrderException($option);
                }
                $status_template = [
                    'id' => $product_id,
                    'count' => $count,
                    'name' => $product_detail['name'],
                    'main_img_url' => $product_detail['main_img_url'],
                    'price' => $product_detail['price'],
                    'total_price' => $count * $product_detail['price'],
                    'stock' => $product_detail['stock'],
                ];
                $product_status[$product_id] = $status_template;
            }
            else
            {
                $option = [
                    'msg' => 'id为' . $product_id . '的商品不存在，订单创建失败'
                ];
                throw new OrderException($option);
            }
        }
        $this->product_status = $product_status;
        return $product_status;
    }

    /**
     * 根据订单查找真实商品
     * @param type $order_products
     * @return Array 
     */
    private function getProductsByOrder($order_products)
    {
        $order_products_id_arr = array_keys($order_products);
        $db_products = Product::where('id', 'in', $order_products_id_arr)
                ->lock(true)
                ->select()
                ->toArray();
        $db_products = array_under_reset($db_products, 'id');
        return $db_products;
    }

    private function getUserAddress()
    {
        $userAddress = UserAddress::where('user_id', '=', $this->uid)
                ->find();
        if (!$userAddress)
        {
            $option = [
                'msg' => '用户收货地址不存在，下单失败',
                'errorCode' => 60001,
            ];
            throw new UserException($option);
        }
        return $userAddress->toArray();
    }

    /**
     * 根据商品状态,生成订单快照
     * @return type 
     */
    private function snapOrder()
    {
//        获取第一个商品
        $fisrst_product = array_one($this->db_products);
        $snap = [
            'order_price' => 0,
            'total_count' => 0,
            'snap_items' => [],
            'snap_address' => json_encode($this->getUserAddress()),
            'snap_name' => $fisrst_product['name'],
            'snap_img' => $fisrst_product['main_img_url'],
        ];
//        多商品情况
        $count = count($this->db_products);
        if ($count > 1)
        {
            $snap['snap_name'] .= '等';
        }

        foreach ($this->product_status as $product_id => $product_detail)
        {
            $snap['order_price'] += $product_detail['total_price'];
            $snap['total_count'] += $product_detail['count'];
            $snap['snap_items'][$product_id] = $product_detail;
        }
        $snap['snap_items'] = json_encode($snap['snap_items']);
        return $snap;
    }

    public static function makeOrderNo()
    {
        $yCode = array ( 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' );
        $orderSn = $yCode[intval(date('Y')) - 2017] . strtoupper(dechex(date('m'))) . date(
                        'd') . substr(time(), -5) . substr(microtime(), 2, 5) . sprintf(
                        '%02d', rand(0, 99));
        return $orderSn;
    }

}
